//
//
#ifndef Line2D_h__
#define Line2D_h__

#include "Point2D.h"
#include "Vector2D.h"

namespace Geometric
{
	// Line 2D
	class _GEOMETRIC Line2D
	{
	public:
		// coefficient of linear equation: ax+by+c=0
		double m_a;
		double m_b;
		double m_c;

	public:
		Line2D()
		{
			m_a = m_b = m_c = 0;
		}

		Line2D(double _a, double _b, double _c)
			:m_a(_a), m_b(_b), m_c(_c)
		{
		}

		Line2D(const Line2D& _line) 
			:m_a(_line.m_a), m_b(_line.m_b), m_c(_line.m_c)
		{
		}

		Line2D(const Point2D& _point, const Vector2D& _vector)
			:m_a(-_vector.m_dir_y), m_b(_vector.m_dir_x), m_c(m_a*_point.m_x - m_b*_point.m_y)
		{
		}

		Line2D(const Point2D& _start, const Point2D& _end)
		{
			set(_start, _end);
		}

		Line2D(double _pt_x, double _pt_y, double _dir_x, double _dir_y)
		{
			set(_pt_x, _pt_y, _dir_x, _dir_y);
		}

		Line2D(const Point2D& _point, double _dir_x, double _dir_y)
			:m_a(-_dir_y), m_b(_dir_x), m_c(m_a*_point.m_x - m_b*_point.m_y)
		{
		}

		Line2D(double _pt_x, double _pt_y, const Vector2D& _vector)
			:m_a(-_vector.m_dir_y), m_b(_vector.m_dir_x), m_c(m_a*_pt_x - m_b*_pt_y)
		{
		}

		~Line2D()
		{
		}

	public:
		inline Line2D& operator = (const Line2D& _line)
		{
			m_a = _line.m_a;
			m_b = _line.m_b;
			m_c = _line.m_c;
			return (*this);
		}

	public:
		inline void set(const Point2D& _start, const Point2D& _end)
		{
			m_a = _start.m_y - _end.m_y;
			m_b = _end.m_x - _start.m_x;
			m_c = m_a*_start.m_x - m_b*_start.m_y;
		}

		inline void set(double _pt_x, double _pt_y, double _dir_x, double _dir_y)
		{
			m_a = -_dir_y;
			m_b = _dir_x;
			m_c = m_a*_pt_x - m_b*_pt_y;
		}

		// get the cross product
		inline double getCross(double _a, double _b)
		{
			return (m_a * _b - m_b * _a);
		}

		// if the two line is parallel
		inline bool isParallel(double _dir_x, double _dir_y)
		{
			double cross = getCross(_dir_x, _dir_y);
			return Gmath::isZero(cross);
		}

		inline bool isParallel(const Line2D& _line)
		{
			return isParallel(_line.m_a, _line.m_b);
		}

		// if the two line is vertical
		inline bool isVertical(double _dir_x, double _dir_y)
		{
			double dot = m_a * _dir_x + m_b * _dir_y;
			return Gmath::isZero(dot);
		}

		inline bool isVertical(const Line2D& _line)
		{
			return isVertical(_line.m_a, _line.m_b);
		}

		// if the point is on the line
		inline bool isPointOnLine(double _x, double _y)
		{
			double result = m_a*_x + m_b*_y + m_c;
			return Gmath::isZero(result);
		}

		inline bool isPointOnLine(const Point2D& _point)
		{
			return isPointOnLine(_point.m_x, _point.m_y);
		}

		// if the two lines is overlapped
		inline bool isOverlap(double _a, double _b, double _c)
		{
			double cross1 = getCross(_a, _b);
			if(Gmath::isZero(cross1) == true)
			{
				double cross2 = m_a*_c - m_c*_a;
				return Gmath::isZero(cross2);
			}
			return false;
		}

		inline bool isOverlap(const Line2D& _line)
		{
			return isOverlap(_line.m_a, _line.m_b, _line.m_c);
		}

		// solve slope of the line
		inline bool getSlope(double& _slope)
		{
			if(Gmath::isZero(m_b) == false)
			{
				_slope = -(m_a / m_b);
				return true;
			}
			return false;
		}

		// solve the x according to specify y
		inline bool getCoordinateX(double& _x, double _y)
		{
			if(Gmath::isZero(m_a) == false)
			{
				_x = (- m_b*_y - m_c) / m_a;
				return true;
			}
			return false;
		}

		// solve the y according to specify x
		inline bool getCoordinateY(double _x, double& _y)
		{
			if(Gmath::isZero(m_b) == false)
			{
				_y = (- m_a*_x - m_c) / m_b;
				return true;
			}
			return false;
		}

		// solve the angle according to specify vector
		inline double getAngle(double _dir_x, double _dir_y)
		{
			Vector2D vec(m_b, -m_a);
			double angle = vec.getAngle(_dir_x, _dir_y);
			if(Gmath::isLess(MATH_CONST_PI_DIV2, angle) == true)
			{
				return (MATH_CONST_PI - angle);
			}
			return angle;
		}

		// solve the distance of a point to the line
		inline double getDistance(double _x, double _y)
		{
			return ((m_a*_x + m_b*_y + m_c) / Gmath::toSqrt(m_a*m_a + m_b*m_b));
		}

		inline double getDistance(const Point2D& _point)
		{
			return getDistance(_point.m_x, _point.m_y);
		}
		
		// solve the intersect point between two lines
		inline int getIntersectPoint(Line2D& _line, Point2D& _point)
		{
			double base = m_a*_line.m_b - m_b*_line.m_a;

			if(Gmath::isZero(base) == false)
			{
				_point.m_x = (m_b*_line.m_c - m_c*_line.m_b) / base;
				_point.m_y = (m_c*_line.m_a - m_a*_line.m_c) / base;
				return LINE_LINE_INTERSECT;
			}
			else
			{
				return (isOverlap(_line) == true 
					? LINE_LINE_OVERLAP : LINE_LINE_PARALLEL);
			}
		}

		//
		inline bool getVerticalPoint(const Point2D& _point, Point2D& _ver_point)
		{
			if(isPointOnLine(_point) == false)
			{
				Line2D ver_line(_point.m_x, _point.m_y, -m_b, m_a);
				return (getIntersectPoint(ver_line, _ver_point) == LINE_LINE_INTERSECT
					? true : false);
			}
			return false;
		}
	};

}


#endif